/* Copyright (C) 2021 Rain, Calvinlin, Wangguanbo */

/* This file is part of Panda. */

/* 
  Panda is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  Panda is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with Panda. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#include <assert.h>

#include <panda.h>
#include <defines.h>
#include <syntax.h>


/*
 * get_word(): get next word from fd. 
 * 
 * fd: file descriptor of input file. 
 * buffer: word buffer (output only)
 *
 * return value: length of this word. 
 *
 * WARNING: buffer maybe overflow when this word is to big. 
 * */


int get_word(int fd, char *buffer) {
	int i;

	/* isstr = 0: no '\"' find. 
	 * isstr = 1: found a '\"' (is a string). 
	 * */
	char isstr = 0;

	for (i = 0; i < MAX_WORD_LEN;i++) {
		if (!read(fd, buffer + i, 1)) {
			/* end of file */
			buffer[i] = 0x00;
			return 0;
		}

		if (buffer[i] == '\"') {
			if (!isstr) {
				isstr = 1;
				continue;
			}

			return WTYPE_STR;
		}

		/* if it's not a letter */
		if ((buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n'
			|| buffer[i] == '\r') && !isstr) {

			if (!i) {
				/* the first character of this word is like '\n', so 
				 * we get next word
				 * */

				return get_word(fd, buffer);
			}


			buffer[i] = 0x00;
			return WTYPE_UNKNOW;
		}

		/*
		 * if it's an operator (like '+'), and this isn't first character...
		 * this: 'abcdef+' we should return 'abcdef', not 'abcdef+'  */

		/* 
		 * if this is first character ('+abcdef'), we should 
		 * return '+', not '+abcdef'
		 * */

		if (is_operator(buffer[i]) && i && !isstr) {
			/* but we shouldn't ignore operators (this: get(a), we 
			 * shouldn't ignore '(' or ')' ) 
			 * */

			buffer[i] = 0x00;
			lseek(fd, -1, SEEK_CUR);

			return WTYPE_UNKNOW;
		}

		if (is_operator(buffer[i]) && !i && !isstr) 
			return WTYPE_OPRT;
		
	}

	/* i >= MAX_WORD_LEN */
	return 0;
}



/* 
 * syn_word(): get next word and create a syntax object for it. 
 *
 * return value: syntax object for this word. 
 *
 * WARNING: you must free `return value`. 
 * */


struct panda_syn_obj *syn_word(int fd) {

#define KEYWORDS_COUNT 5
	static char keywords[KEYWORDS_COUNT][MAX_WORD_LEN] = {
		"var", 
		"while", 
		"for", 
		"if", 
		"else"
	};

	char buffer[MAX_WORD_LEN] = {0};
	int i;

	/* buffer[0] is a digit */
	int maybe_number = 1;

	/* maybe this word is a keyword */
	int maybe_keyword = 1;
	
	int is_keyword = 0;

	struct panda_syn_obj *syn_obj = malloc(sizeof(struct panda_syn_obj));
	assert(syn_obj);

	memset(syn_obj, 0, sizeof(struct panda_syn_obj));

	int wtype = get_word(fd, buffer);

	if (!wtype) 
		return NULL;


	if (wtype == WTYPE_UNKNOW) {

		for (i = 0; buffer[i]; i++) {
			char c = buffer[i];
		
			/* it must be a value */
			if (is_digit(c) && maybe_number) {
				/* type of this word is a number */
				syn_obj->type = OBJTYPE_NUMBER;

				/* a keyword shouldn't have any number */
				maybe_keyword = 0;

				continue;
			}

			else if (is_letter(c) && maybe_keyword) {
				maybe_number = 0;

				continue;
			}

			else if (is_letter(c) && maybe_number && i) {
				/* it's a letter, it should be a number and it's not first character */
				syntax_error("invalid number format '%s'\n", buffer);
				return NULL;
			}

			/* it's not a keyword or a number, raise syntax error */
			else {
				syntax_error("not a valid name or number '%s'\n", buffer);
				return NULL;
			}
		}

		/* if this a keyword? */

		if (maybe_keyword) {
			/* compare all keywords, if matches, it's a keyword. */
			/* else, it must be a variable/function. */

			int j;

			for (j = 0; j < KEYWORDS_COUNT; j++) {
				if (strncmp(buffer, keywords[j], MAX_WORD_LEN) == 0) {
					is_keyword = 1;
					syn_obj->type = OBJTYPE_KEYWORD;
	
					goto end_for;
				}
			}

			/* not a key word */
			syn_obj->type = OBJTYPE_NAME;
		}

		if (maybe_number) {
			syn_obj->type = OBJTYPE_NUMBER;
			syn_obj->value = atoi(buffer);
		}
	}


end_for:
	if (!maybe_number) {
		/* copy buffer to name */
		syn_obj->name = (char *) malloc(strlen(buffer) + 1);
		strcpy(syn_obj->name, buffer);
	}

	printf("%s: ", buffer);

	if (wtype == WTYPE_STR) 
		printf("string\n");

	else if (wtype == WTYPE_OPRT) 
		printf("operator\n");

	else if (maybe_number) {
		wtype = WTYPE_NUMBER;
		printf("number\n");
	}


	else if (is_keyword) {
		wtype = WTYPE_KWORD;
		printf("keyword\n");
	}

	else if (!is_keyword && !maybe_number) {
		wtype = WTYPE_NAME;
		printf("variable / function name\n");
	}


	syn_obj->object = create_obj(wtype);


	return syn_obj;
}


/* 
 * create_obj: create a object for word type wtype. 
 * */

void *create_obj(int wtype) {
	void *obj;

#define ALLOC_OF(type) obj = (void *) malloc(sizeof(type))

	switch (wtype) {
		case WTYPE_NUMBER:
			ALLOC_OF(struct panda_obj_rvalue);
			break;

		case WTYPE_KWORD:
			ALLOC_OF(struct panda_obj_keyword);
			break;

		case WTYPE_OPRT:
			ALLOC_OF(struct panda_obj_operator);
			break;

		case WTYPE_NAME:
			ALLOC_OF(struct panda_obj_name);
			break;
	}

	return obj;

#undef ALLOC_OF

}



int is_operator(char c) {
	static char operators[] = {
		'~', '!', '%', '^', '&', '*', '(', ')', 
		'-', '=', '+', '[', ']', '{', '}', '|', 
		';', '\'', '\"', ',', '.', '<', '>', '/', 
		0x00
	};

	int i;

	for (i = 0; operators[i]; i++) {
		/* it's an operator */
		if (c == operators[i]) 
			return 1;
	}

	return 0;
}
